package com.itaming.lycheeframework.support.utils;

import lombok.experimental.UtilityClass;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * Reflect工具类
 *
 * @author A.Ming
 */
@UtilityClass
public class ReflectionUtil {

    /**
     * 类方法缓存
     */
    private static final Map<Class<?>, Method[]> declaredMethodsCache = new ConcurrentReferenceHashMap<>(256);

    /**
     * 类字段缓存
     */
    private static final Map<Class<?>, Field[]> declaredFieldsCache = new ConcurrentReferenceHashMap<>(256);

    /**
     * 创建实例
     *
     * @param clazz 类
     * @return 实例
     */
    public static <T> T newInstance(Class<T> clazz) {
        try {
            Constructor<T> constructor = getConstructor(clazz);
            return constructor.newInstance();
        } catch (Exception e) {
            throw new IllegalStateException("Create instance for class \"" + clazz.getName() + "\" failed", e);
        }
    }

    /**
     * 创建实例
     *
     * @param clazz      类
     * @param parameters 构造函数参数
     * @return 实例
     */
    public static <T> T newInstance(Class<T> clazz, Object... parameters) {
        try {
            Class<?>[] classes = ClassUtil.getClasses(parameters);
            Constructor<T> constructor = getConstructor(clazz, classes);
            return constructor.newInstance(parameters);
        } catch (Exception e) {
            throw new IllegalStateException("Create instance for class \"" + clazz.getName() + "\" failed", e);
        }
    }

    /**
     * 获取构造函数
     *
     * @param clazz          类
     * @param parameterTypes 参数类型
     * @return 构造函数
     */
    public static <T> Constructor<T> getConstructor(Class<T> clazz, Class<?>... parameterTypes) {
        Assert.notNull(clazz, "Class must not be null");

        if (clazz.isInterface()) {
            throw new IllegalArgumentException("Class cannot be an interface");
        }

        try {
            return clazz.getDeclaredConstructor(parameterTypes);
        } catch (Exception e) {
            ReflectionUtils.handleReflectionException(e);
            throw new IllegalStateException("Should never get here");
        }
    }

    /**
     * 设置可访问性
     *
     * @param accessibleObject 可访问对象
     */
    public static void setAccessible(AccessibleObject accessibleObject) {
        if (null != accessibleObject) {
            accessibleObject.trySetAccessible();
        }
    }

    /**
     * 查找具有指定注解的方法
     *
     * @param clazz           类
     * @param annotationClass 注解类
     * @return 方法
     */
    public static Method findMethod(Class<?> clazz, Class<? extends Annotation> annotationClass) {
        Assert.notNull(clazz, "Class must not be null");
        Assert.notNull(annotationClass, "Annotation class must not be null");
        Assert.isTrue(annotationClass.isAnnotation(), "Annotation class must be an annotation");

        Method[] methods = getDeclaredMethods(clazz);
        for (Method method : methods) {
            if (method.isAnnotationPresent(annotationClass)) {
                return method;
            }
        }

        return null;
    }

    /**
     * 查找具有指定注解的所有方法
     *
     * @param clazz           类
     * @param annotationClass 注解类
     * @return 方法数组
     */
    public static Method[] findMethods(Class<?> clazz, Class<? extends Annotation> annotationClass) {
        Assert.notNull(clazz, "Class must not be null");
        Assert.notNull(annotationClass, "Annotation class must not be null");
        Assert.isTrue(annotationClass.isAnnotation(), "Annotation class must be an annotation");

        List<Method> result = new ArrayList<>();

        Method[] methods = getDeclaredMethods(clazz);
        for (Method method : methods) {
            if (method.isAnnotationPresent(annotationClass)) {
                result.add(method);
            }
        }

        return result.toArray(ArrayUtil.EMPTY_METHOD_ARRAY);
    }

    /**
     * 查找方法
     *
     * @param clazz          类
     * @param name           方法名
     * @param parameterTypes 参数类型
     * @return 方法
     */
    public static Method findMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
        Assert.notNull(clazz, "Class must not be null");
        Assert.notNull(name, "Method name must not be null");

        for (Class<?> searchType = clazz; searchType != null; searchType = searchType.getSuperclass()) {
            Method[] methods = searchType.isInterface() ? searchType.getMethods() : getDeclaredMethods(searchType, false);
            for (Method method : methods) {
                if (name.equals(method.getName()) && (parameterTypes == null || hasSameParams(method, parameterTypes))) {
                    return method;
                }
            }
        }

        return null;
    }

    /**
     * 获取类方法
     *
     * @param clazz 类
     * @return 方法数组
     */
    public static Method[] getDeclaredMethods(Class<?> clazz) {
        return getDeclaredMethods(clazz, true);
    }

    /**
     * 调用方法
     *
     * @param method    方法
     * @param target    目标类对象
     * @param parameter 参数
     * @return 方法返回值
     */
    public static Object invokeMethod(Method method, Object target, Object... parameter) {
        Assert.notNull(method, "Method must not be null");
        Assert.notNull(target, "Target object must not be null");

        try {
            setAccessible(method);
            return method.invoke(target, parameter);
        } catch (Exception e) {
            ReflectionUtils.handleReflectionException(e);
            throw new IllegalStateException("Should never get here");
        }
    }

    /**
     * 调用方法
     *
     * @param clazz     类
     * @param name      方法名
     * @param target    目标类对象
     * @param parameter 参数
     * @return 方法返回值
     */
    public static Object invokeMethod(Class<?> clazz, String name, Object target, Object... parameter) {
        Assert.notNull(clazz, "Class must not be null");
        Assert.notNull(name, "Method name must not be null");
        Assert.notNull(target, "Target object must not be null");

        try {
            Class<?>[] classes = ClassUtil.getClasses(parameter);
            Method method = findMethod(clazz, name, classes);
            if (method == null) {
                throw new NoSuchMethodException(buildNoSuchMethodExceptionMessage(clazz, name, classes));
            }
            setAccessible(method);
            return method.invoke(target, parameter);
        } catch (Exception e) {
            ReflectionUtils.handleReflectionException(e);
            throw new IllegalStateException("Should never get here");
        }
    }

    /**
     * 查找具有指定注解的字段
     *
     * @param clazz           类
     * @param annotationClass 注解类
     * @return 字段
     */
    public static Field findField(Class<?> clazz, Class<? extends Annotation> annotationClass) {
        Assert.notNull(clazz, "Class must not be null");
        Assert.notNull(annotationClass, "Annotation class must not be null");
        Assert.isTrue(annotationClass.isAnnotation(), "Annotation class must be an annotation");

        Field[] fields = getDeclaredFields(clazz, false);
        for (Field field : fields) {
            if (field.isAnnotationPresent(annotationClass)) {
                return field;
            }
        }

        return null;
    }

    /**
     * 查找具有指定注解的所有字段
     *
     * @param clazz           类
     * @param annotationClass 注解类
     * @return 字段数组
     */
    public static Field[] findFields(Class<?> clazz, Class<? extends Annotation> annotationClass) {
        Assert.notNull(clazz, "Class must not be null");
        Assert.notNull(annotationClass, "Annotation class must not be null");
        Assert.isTrue(annotationClass.isAnnotation(), "Annotation class must be an annotation");

        ArrayList<Field> result = new ArrayList<>(4);

        Field[] fields = getDeclaredFields(clazz, true);
        for (Field field : fields) {
            if (field.isAnnotationPresent(annotationClass)) {
                result.add(field);
            }
        }

        return result.toArray(ArrayUtil.EMPTY_FIELD_ARRAY);
    }

    /**
     * 获取类字段
     *
     * @param clazz 类
     * @return 字段数组
     */
    public static Field[] getDeclaredFields(Class<?> clazz) {
        return getDeclaredFields(clazz, true);
    }

    /**
     * 获取字段值
     *
     * @param field  字段
     * @param target 目标类对象
     * @return 字段值
     */
    public static Object getFieldValue(Field field, Object target) {
        Assert.notNull(field, "Field must not be null");
        Assert.notNull(target, "Target object must not be null");

        try {
            setAccessible(field);
            return field.get(target);
        } catch (Exception e) {
            ReflectionUtils.handleReflectionException(e);
            throw new IllegalStateException("Should never get here");
        }
    }

    /**
     * 设置字段值
     *
     * @param field  字段
     * @param target 目标类对象
     * @param value  要设置的值
     */
    public static void setFieldValue(Field field, Object target, Object value) {
        Assert.notNull(field, "Field must not be null");
        Assert.notNull(target, "Target object must not be null");

        try {
            field.set(target, value);
        } catch (IllegalAccessException e) {
            ReflectionUtils.handleReflectionException(e);
        }
    }

    /**
     * 是否具有相同参数
     *
     * @param method     方法
     * @param paramTypes 参数类型
     * @return boolean
     */
    public static boolean hasSameParams(Method method, Class<?>[] paramTypes) {
        return paramTypes.length == method.getParameterCount() && Arrays.equals(paramTypes, method.getParameterTypes());
    }

    /**
     * 是否为<code>equals</code>方法
     *
     * @param method 方法
     * @return boolean
     */
    public static boolean isEqualsMethod(Method method) {
        return method != null && method.getParameterCount() == 1 && method.getName().equals("equals") && method.getParameterTypes()[0] == Object.class;
    }

    /**
     * 是否为<code>hashCode</code>方法
     *
     * @param method 方法
     * @return boolean
     */
    public static boolean isHashCodeMethod(Method method) {
        return method != null && method.getParameterCount() == 0 && method.getName().equals("hashCode");
    }

    /**
     * 是否为<code>toString</code>方法
     *
     * @param method 方法
     * @return boolean
     */
    public static boolean isToStringMethod(Method method) {
        return method != null && method.getParameterCount() == 0 && method.getName().equals("toString");
    }

    /**
     * 清除缓存
     */
    public static void clearCache() {
        declaredMethodsCache.clear();
        declaredFieldsCache.clear();
    }

    /**
     * 获取类方法
     *
     * @param clazz     类
     * @param defensive 是否防御性复制
     * @return 方法数组
     */
    private static Method[] getDeclaredMethods(Class<?> clazz, boolean defensive) {
        Assert.notNull(clazz, "Class must not be null");

        Method[] result = declaredMethodsCache.get(clazz);
        if (result == null) {
            try {
                Method[] declaredMethods = clazz.getDeclaredMethods();
                List<Method> defaultMethods = findDefaultMethodsOnInterfaces(clazz);
                if (CollectionUtil.isNotEmpty(defaultMethods)) {
                    result = new Method[declaredMethods.length + defaultMethods.size()];
                    System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length);
                    int index = declaredMethods.length;

                    for (Method method : defaultMethods) {
                        result[index] = method;
                        index++;
                    }
                } else {
                    result = declaredMethods;
                }

                declaredMethodsCache.put(clazz, result);
            } catch (Exception e) {
                throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() + "] from ClassLoader [" + clazz.getClassLoader() + "]", e);
            }
        }

        return result.length != 0 && defensive ? result.clone() : result;
    }

    /**
     * 获取类字段
     *
     * @param clazz 类
     * @return 字段数组
     */
    private static Field[] getDeclaredFields(Class<?> clazz, boolean defensive) {
        Assert.notNull(clazz, "Class must not be null");

        Field[] result = declaredFieldsCache.get(clazz);
        if (result == null) {
            try {
                result = clazz.getDeclaredFields();
                declaredFieldsCache.put(clazz, result);
            } catch (Throwable e) {
                throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() + "] from ClassLoader [" + clazz.getClassLoader() + "]", e);
            }
        }

        return result.length != 0 && defensive ? result.clone() : result;
    }

    /**
     * 查找默认方法
     *
     * @param clazz 类
     * @return 方法列表
     */
    private static List<Method> findDefaultMethodsOnInterfaces(Class<?> clazz) {
        List<Method> result = new ArrayList<>();
        Class<?>[] interfaces = clazz.getInterfaces();

        for (Class<?> ifc : interfaces) {
            Method[] methods = ifc.getMethods();
            for (Method ifcMethod : methods) {
                if (!Modifier.isAbstract(ifcMethod.getModifiers())) {
                    result.add(ifcMethod);
                }
            }
        }

        return result;
    }

    /**
     * 构建{@link NoSuchMethodException}异常信息
     *
     * @param clazz      类
     * @param methodName 方法名
     * @param parameters 参数
     * @return 异常信息
     */
    private static String buildNoSuchMethodExceptionMessage(Class<?> clazz, String methodName, Class<?>[] parameters) {
        String joinedClassNames = StringUtil.join(StringPool.COMMA_SPACE, ArrayUtil.map(parameters, Class::getName));
        return String.format("%s.%s(%s)", clazz.getName(), methodName, joinedClassNames);
    }

}
